GDB attach 加载带调试信息的共享库符号
进程运行时链接的共享库若是 stripped 版本(无调试信息),GDB attach 后就看不到库函数的源码和行号断点。解决路线有两条:手头只有零星几个带符号的库时,用 add-symbol-file 把它们附加到已加载地址上;若产品提供了配套的分离调试包,用 set debug-file-directory 让 GDB 到指定目录批量加载。本文以 libzmq 为例演示前者,并解释为什么直觉方案 solib-search-path 在这个场景下不生效——这是最常见的踩坑点;文末补充调试包的标准用法。
问题
进程加载的是 /lib/aarch64-linux-gnu/libzmq.so.5(无调试信息),手头另有一份编译时保留了调试信息的 libzmq.so.5,想让 attach 上的 GDB 使用后者加载符号。
关键认知
GDB attach 不能改变进程的库加载路径。 进程启动时 execve 和动态链接器已完成路径解析,/proc/<pid>/maps 中的映射不可更改。GDB 能做的只是替换符号文件的查找来源。
方案对比
| 方案 | 原理 | 优缺点 |
|---|---|---|
set solib-search-path | 按文件名在指定目录搜索符号文件 | 仅在原始路径文件不存在时回退搜索,本地文件存在时无效 |
set sysroot | 路径前缀重定向 | 需要构造完整目录结构,影响所有库的查找 |
add-symbol-file | 手动指定符号文件和加载地址 | 最直接,不影响其他库,推荐 |
| 临时替换原文件 | 替换系统目录下的 so | 暴力有效,但影响全局,需恢复 |
推荐方案:add-symbol-file
步骤
- 查看目标库的加载地址
(gdb) info sharedlibrary libzmq
From To Syms Read Shared Object Library
0x0000ffffa18124e0 0x0000ffffa186f344 Yes (*) /lib/aarch64-linux-gnu/libzmq.so.5 起始地址 0x0000ffffa18124e0 即 .text 段地址。
- 加载带调试信息的符号文件
(gdb) add-symbol-file /home/data/libzmq/libzmq.so.5 0x0000ffffa18124e0
add symbol table from file "/home/data/libzmq/libzmq.so.5" at
.text_addr = 0xffffa18124e0
(y or n) y
Reading symbols from /home/data/libzmq/libzmq.so.5... - 验证符号加载成功
(gdb) info functions zmq_send
All functions matching regular expression "zmq_send":
File /home/user/workspace/libzmq/src/zmq.cpp:
356: int zmq_send(void*, void const*, size_t, int);
383: int zmq_send_const(void*, void const*, size_t, int);
415: int zmq_sendiov(void*, iovec*, size_t, int);
351: int zmq_sendmsg(void*, zmq_msg_t*, int); 能列出源码位置和函数签名,说明符号生效。
- 正常调试
(gdb) b zmq_send
(gdb) b zmq.cpp:356
(gdb) list zmq_send 注意事项
info sharedlibrary中该库仍显示Yes (*),这是正常的——它反映的是进程原始加载的 so 的状态,不影响add-symbol-file附加的符号- 如果
info functions找不到函数,说明指定的 so 本身也是 stripped 的,需要确认文件是否包含调试信息:
readelf -S /home/data/libzmq/libzmq.so.5 | grep debug solib-search-path 为什么无效
set solib-search-path 的工作机制:
- GDB 取进程已加载库的文件名(如
libzmq.so.5) - 先尝试原始绝对路径
/lib/aarch64-linux-gnu/libzmq.so.5 - 只有原始路径文件不存在时,才回退到
solib-search-path指定的目录搜索
由于 /lib/aarch64-linux-gnu/libzmq.so.5 在本地存在,GDB 直接使用了它,solib-search-path 被跳过。
sysroot 方案(备选)
当需要批量替换多个库的符号时,构造 sysroot 目录结构更高效:
# 构造与原始路径一致的目录结构
mkdir -p /home/data/sysroot/lib/aarch64-linux-gnu/
cp /home/data/libzmq/libzmq.so.5.2.2 /home/data/sysroot/lib/aarch64-linux-gnu/
cd /home/data/sysroot/lib/aarch64-linux-gnu/
ln -sf libzmq.so.5.2.2 libzmq.so.5
ln -sf libzmq.so.5 libzmq.so (gdb) set sysroot /home/data/sysroot
(gdb) set solib-search-path /lib/aarch64-linux-gnu:/opt/deps/lib solib-search-path 作为补充,让 GDB 能找到 sysroot 中未覆盖的其他库。
分离调试包:debug-file-directory 路线
不少产品(包括主流 Linux 发行版的 debug 包)会把调试信息从发布二进制中剥离,另行提供一套「调试包」——目录结构与整包一致,里面只有 .debug 符号文件。这时不需要逐个 add-symbol-file,用 GDB 的 set debug-file-directory 一次性指向调试包目录即可。
符号分离后直接 gdb,会看到这样的预期现象(不是故障):
Reading symbols from ./datacenter...
(No debugging symbols found in ./datacenter)
(gdb) bt
#0 0x0000fffff79a3944 in __pthread_clockjoin_ex () from /lib/.../libpthread.so.0
#2 0x0000aaaaaaaae404 in ?? () 自有程序与动态库的堆栈帧只剩地址(??),仅系统库的导出函数还显示名称;addr2line、objdump --syms 同样查不到符号——符号已全部移入调试包,配置加载后即可恢复。
解压调试包后,三种配置方式:
# 方式一:写入 ~/.gdbinit,一次配置对所有调试会话生效
echo 'set debug-file-directory /home/data/debug-info' >> ~/.gdbinit
gdb /path/to/datacenter # 方式二:已启动的会话内设置。注意:仅 set 不会重新加载符号,须以 file 命令重读
(gdb) set debug-file-directory /home/data/debug-info
(gdb) file /path/to/datacenter # 方式三:启动时 -ex 指定,两条 -ex 顺序不可颠倒,file 不可省略
gdb -ex "set debug-file-directory /home/data/debug-info" -ex "file ./datacenter" 方式三的顺序要求值得强调:位置参数的符号加载先于 -ex 执行,把 file 写成位置参数会导致 set 不生效,这是 -ex 用法最常见的坑。
常见问题对照:
| 现象 | 处理 |
|---|---|
| 堆栈全部为地址,无函数名 | 符号全剥离的预期行为,按上表配置调试包 |
有函数名但无行号(形如 Run() ()) | 旧版本产物,换对应版本的调试包 |
set debug-file-directory 未生效 | 设置须在符号加载前生效;会话内设置后必须 file 重读 |
| 提示 debug information does not match | 调试包与二进制版本不匹配,用 readelf -n 比对双方 Build ID 后更换 |
与 add-symbol-file 的分工:调试包覆盖整套二进制时用 debug-file-directory(版本须严格一致);只是零星补一两个库的符号、或版本对不上需要单独指定时,用 add-symbol-file。
精确获取 .text 段地址
info sharedlibrary 显示的起始地址通常就是 .text 段地址。如需精确确认:
cat /proc/<pid>/maps | grep libzmq 输出中 r-xp(可读可执行)权限的行即为 .text 段:
ffffa18124e0-ffffa186f344 r-xp 00000000 ... /lib/aarch64-linux-gnu/libzmq.so.5 要点
- attach 后无法改变进程的库加载路径,只能替换 GDB 的符号查找来源
- 零星补符号用
add-symbol-file:一行命令,不影响其他库,不需要修改任何文件 - 整套分离调试包用
set debug-file-directory:会话内设置后必须file重读才生效 info sharedlibrary的(*)标记不影响实际调试,以info functions和断点设置验证为准solib-search-path在原始路径文件存在时不会生效,这是最常见的踩坑点- 调试包版本必须与二进制一致,
readelf -n比对 Build ID 是标准校验方法